﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DatabasePage.xaml.cs" company="Mar3ek">
//   This code is licensed under the Mozilla Public License 2.0.
//   You can find the full license text at the following address:
//   https://winkee.codeplex.com/license
// </copyright>
// <summary>
//   An empty page that can be used on its own or navigated to within a Frame.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace WinKee.Pages
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Windows.Input;

    using Windows.Phone.UI.Input;
    using Windows.Storage;
    using Windows.UI.Popups;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Navigation;

    using WinKee.Common;
    using WinKee.Models;

    /// <summary>
    /// The database page.
    /// </summary>
    public sealed partial class DatabasePage : INotifyPropertyChanged
    {
        #region Fields

        /// <summary>
        /// The navigation helper.
        /// </summary>
        private readonly NavigationHelper navigationHelper;

        /// <summary>
        /// The list containing all the password entries. Only used in list navigation mode.
        /// </summary>
        private readonly List<PasswordEntry> originalCollection = new List<PasswordEntry>();

        /// <summary>
        /// The folder navigation stack. Only used in folder navigation mode.
        /// </summary>
        private readonly Stack<PasswordGroup> folderNavigationStack = new Stack<PasswordGroup>();

        /// <summary>
        /// The search string. Only used in list navigation mode.
        /// </summary>
        private string searchString;

        /// <summary>
        /// The list containing the list of items in the current folder. Only used in folder navigation mode.
        /// </summary>
        private IList<object> currentItems;

        /// <summary>
        /// The breadcrumb path. Only used in folder navigation mode. 
        /// </summary>
        private string breadcrumb;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="DatabasePage"/> class.
        /// </summary>
        public DatabasePage()
        {
            this.InitializeComponent();

            this.navigationHelper = new NavigationHelper(this);
            this.navigationHelper.LoadState += this.NavigationHelperLoadState;
            this.navigationHelper.SaveState += this.NavigationHelperSaveState;
            this.LockCommand = new RelayCommand(this.LockAction);
            this.ClearFilterCommand = new RelayCommand(this.ClearFilterAction);
            this.ShowAboutCommand = new RelayCommand(this.ShowAboutAction);
            this.SwitchCommand = new RelayCommand(this.SwitchAction);
            this.SettingsCommand = new RelayCommand(this.ShowSettingsAction);
            this.ToFolderViewCommand = new RelayCommand(this.SwitchToFolderView);
            this.ToListViewCommand = new RelayCommand(this.SwitchToListView);

            // prevent multiple hooks, see http://stackoverflow.com/questions/937181/c-sharp-pattern-to-prevent-an-event-handler-hooked-twice
            HardwareButtons.BackPressed -= this.HardwareButtonsOnBackPressed;
            HardwareButtons.BackPressed += this.HardwareButtonsOnBackPressed;

            var viewSetting = ApplicationData.Current.LocalSettings.ReadSetting(Definitions.DefaultViewSettingKey, false);

            if (viewSetting)
            {
                this.SwitchToFolderView();
            }
            else
            {
                this.SwitchToListView();
            }
            
        }

        #endregion

        #region Public Events

        /// <summary>
        /// The property changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the breadcrumb.
        /// </summary>
        public string Breadcrumb
        {
            get
            {
                return this.breadcrumb;
            }

            set
            {
                this.breadcrumb = value;
                this.OnPropertyChanged();
            }
        }

        /// <summary>
        /// Gets the lock command.
        /// </summary>
        public ICommand LockCommand { get; private set; }

        /// <summary>
        /// Gets the settings command.
        /// </summary>
        public ICommand SettingsCommand { get; private set; }

        /// <summary>
        /// Gets the switch command.
        /// </summary>
        public ICommand SwitchCommand { get; private set; }

        /// <summary>
        /// Gets the <see cref="NavigationHelper"/> associated with this <see cref="Page"/>.
        /// </summary>
        public NavigationHelper NavigationHelper
        {
            get
            {
                return this.navigationHelper;
            }
        }

        /// <summary>
        /// Gets the original collection.
        /// </summary>
        public List<PasswordEntry> OriginalCollection
        {
            get
            {
                return this.originalCollection;
            }
        }

        /// <summary>
        /// Gets or sets the search string.
        /// </summary>
        public string SearchString
        {
            get
            {
                return this.searchString;
            }

            set
            {
                this.searchString = value;
                this.FilterCollection();
                this.OnPropertyChanged();
            }
        }

        /// <summary>
        /// Gets the clear filter command.
        /// </summary>
        public ICommand ClearFilterCommand { get; private set; }

        /// <summary>
        /// Gets the show about command.
        /// </summary>
        public ICommand ShowAboutCommand { get; private set; }

        /// <summary>
        /// Gets the to folder view command.
        /// </summary>
        public ICommand ToFolderViewCommand { get; private set; }

        /// <summary>
        /// Gets the to list view command.
        /// </summary>
        public ICommand ToListViewCommand { get; private set; }

        /// <summary>
        /// Gets the root group.
        /// </summary>
        public PasswordGroup RootGroup { get; private set; }

        /// <summary>
        /// Gets the current items.
        /// </summary>
        public IList<object> CurrentItems
        {
            get
            {
                return this.currentItems;
            }

            private set
            {
                this.currentItems = value;
                this.OnPropertyChanged();
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Handles the back button press.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="backPressedEventArgs">
        /// The event arguments.
        /// </param>
        internal void HardwareButtonsOnBackPressed(object sender, BackPressedEventArgs backPressedEventArgs)
        {
            if (this.folderNavigationStack.Count <= 1)
            {
                return;
            }

            backPressedEventArgs.Handled = true;
            this.folderNavigationStack.Pop();

            var previous = this.folderNavigationStack.Peek();
            this.SetCurrentItems(previous);
            this.SetBreadcrumb();
        }

        /// <summary>
        /// The on navigated from.
        /// </summary>
        /// <param name="e">
        /// The e.
        /// </param>
        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            HardwareButtons.BackPressed -= this.HardwareButtonsOnBackPressed;
            this.navigationHelper.OnNavigatedFrom(e);
        }

        /// <summary>
        /// The methods provided in this section are simply used to allow
        /// NavigationHelper to respond to the page's navigation methods.
        /// <para>
        /// Page specific logic should be placed in event handlers for the  
        /// <see cref="NavigationHelper.LoadState"/>
        /// and <see cref="NavigationHelper.SaveState"/>.
        /// The navigation parameter is available in the LoadState method 
        /// in addition to page state preserved during an earlier session.
        /// </para>
        /// </summary>
        /// <param name="e">
        /// Provides data for navigation methods and event
        /// handlers that cannot cancel the navigation request.
        /// </param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            HardwareButtons.BackPressed -= this.HardwareButtonsOnBackPressed;
            HardwareButtons.BackPressed += this.HardwareButtonsOnBackPressed;
            this.Frame.BackStack.Clear();
            this.SearchBox.Focus(FocusState.Pointer);
            this.navigationHelper.OnNavigatedTo(e);
        }

        /// <summary>
        /// The filter collection.
        /// </summary>
        private void FilterCollection()
        {
            if (this.OriginalCollection.Count == 0)
            {
                return;
            }

            if (string.IsNullOrEmpty(this.searchString))
            {
                this.PasswordEntries.Source =
                    this.OriginalCollection.Where(en => !string.IsNullOrEmpty(en.Title))
                        .OrderBy(en => en.Title)
                        .GroupBy(en => en.GroupingKey)
                        .OrderBy(g => g.Key)
                        .ToList();
                ((ListViewBase)this.ZoomControl.ZoomedOutView).ItemsSource = this.PasswordEntries.View.CollectionGroups;
            }
            else
            {
                this.PasswordEntries.Source =
                    this.OriginalCollection.Where(
                        en =>
                        (!string.IsNullOrEmpty(en.Title) && en.Title.ToUpper().Contains(this.searchString.ToUpper()))
                        || (!string.IsNullOrEmpty(en.UserName)
                            && en.UserName.ToUpper().Contains(this.searchString.ToUpper())))
                        .OrderBy(en => en.Title)
                        .GroupBy(en => en.GroupingKey)
                        .OrderBy(g => g.Key)
                        .ToList();
                ((ListViewBase)this.ZoomControl.ZoomedOutView).ItemsSource = this.PasswordEntries.View.CollectionGroups;
            }
        }

        /// <summary>
        /// The list view base_ on item click.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void ListViewBase_OnItemClick(object sender, ItemClickEventArgs e)
        {
            var entry = e.ClickedItem as PasswordEntry;
            this.Frame.Navigate(typeof(EntryDetails), entry.Serialize());
        }

        /// <summary>
        /// The lock action.
        /// </summary>
        private void LockAction()
        {
            this.SearchString = null;
            this.originalCollection.Clear();
            Application.Current.Exit();
        }

        /// <summary>
        /// Populates the page with content passed during navigation.  Any saved state is also
        /// provided when recreating a page from a prior session.
        /// </summary>
        /// <param name="sender">
        /// The source of the event; typically <see cref="NavigationHelper"/>
        /// </param>
        /// <param name="e">
        /// Event data that provides both the navigation parameter passed to
        /// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested and
        /// a dictionary of state preserved by this page during an earlier
        /// session.  The state will be null the first time a page is visited.
        /// </param>
        private void NavigationHelperLoadState(object sender, LoadStateEventArgs e)
        {
            HardwareButtons.BackPressed -= this.HardwareButtonsOnBackPressed;
            HardwareButtons.BackPressed += this.HardwareButtonsOnBackPressed;
            this.LoadEntries(MainPage.LoadedRoot);

            if (e.PageState != null)
            {
                object filter;
                e.PageState.TryGetValue("Filter", out filter);
                this.searchString = filter as string;

                object navigationStack;
                e.PageState.TryGetValue("NavigationStack", out navigationStack);

                var deserializedStack = ((string)navigationStack).Deserialize<PasswordGroup[]>();

                if (deserializedStack != null)
                {
                    foreach (var item in deserializedStack.Reverse().Skip(1))
                    {
                        this.folderNavigationStack.Push(item);
                    }

                    var current = deserializedStack[0];
                    this.SetCurrentItems(current);
                }
            }

            this.SetBreadcrumb();
            this.FilterCollection();
        }

        private void LoadEntries(PasswordGroup root)
        {
            if (root == null)
            {
                return;
            }

            this.RootGroup = root;

            this.SetCurrentItems(this.RootGroup);

            if (this.folderNavigationStack.Count == 0)
            {
                this.folderNavigationStack.Push(this.RootGroup);
            }

            var queue = new Queue<PasswordGroup>();
            queue.Enqueue(this.RootGroup);

            while (queue.Count > 0)
            {
                var g = queue.Dequeue();

                foreach (var child in g.Groups)
                {
                    queue.Enqueue(child);
                }

                this.originalCollection.AddRange(g.Entries);
            }
        }

        /// <summary>
        /// Preserves state associated with this page in case the application is suspended or the
        /// page is discarded from the navigation cache.  Values must conform to the serialization
        /// requirements of <see cref="SuspensionManager.SessionState"/>.
        /// </summary>
        /// <param name="sender">
        /// The source of the event; typically <see cref="NavigationHelper"/>
        /// </param>
        /// <param name="e">
        /// Event data that provides an empty dictionary to be populated with
        /// serializable state.
        /// </param>
        private void NavigationHelperSaveState(object sender, SaveStateEventArgs e)
        {
            e.PageState["Filter"] = this.searchString;
            e.PageState["NavigationStack"] = this.folderNavigationStack.ToArray().Serialize();
        }

        /// <summary>
        /// The on property changed.
        /// </summary>
        /// <param name="propertyName">
        /// The property name.
        /// </param>
        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = this.PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// Clears the search filter.
        /// </summary>
        private void ClearFilterAction()
        {
            this.SearchString = string.Empty;
        }

        /// <summary>
        /// Shows the about page.
        /// </summary>
        private void ShowAboutAction()
        {
            this.Frame.Navigate(typeof(AboutPage));
        }

        #endregion

        /// <summary>
        /// Handles the GotFocus event for the search box.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The event arguments.
        /// </param>
        private void SearchBoxGotFocus(object sender, RoutedEventArgs e)
        {
            this.SearchBox.SelectAll();
        }

        /// <summary>
        /// Navigates to the settings page.
        /// </summary>
        private void ShowSettingsAction()
        {
            this.Frame.Navigate(typeof(SettingsPage));
        }

        /// <summary>
        /// Locks the current database and navigates back to database selection page.
        /// </summary>
        private async void SwitchAction()
        {
            var question = new MessageDialog("This will lock and close your current database. Do you wish to continue?", "Switch databases");
            question.Commands.Add(new UICommand("Yes", this.LockDatabase));
            question.Commands.Add(new UICommand("No", command => { }));

            await question.ShowAsync();
        }

        /// <summary>
        /// Locks the database.
        /// </summary>
        /// <param name="command">
        /// The command.
        /// </param>
        private void LockDatabase(IUICommand command)
        {
            this.Frame.BackStack.Clear();
            this.OriginalCollection.Clear();
            this.searchString = string.Empty;
            this.Frame.Navigate(typeof(MainPage));
        }

        /// <summary>
        /// Switches to the folder navigation mode.
        /// </summary>
        private void SwitchToFolderView()
        {
            ApplicationData.Current.LocalSettings.Values[Definitions.DefaultViewSettingKey] = true;
            VisualStateManager.GoToState(this, "ByFolderState", true);
            this.ToFolderViewButton.Visibility = Visibility.Collapsed;
            this.ToListViewButton.Visibility = Visibility.Visible;
        }

        /// <summary>
        /// Switches to the list navigation mode.
        /// </summary>
        private void SwitchToListView()
        {
            ApplicationData.Current.LocalSettings.Values[Definitions.DefaultViewSettingKey] = false;
            VisualStateManager.GoToState(this, "ByNameState", true);
            this.ToFolderViewButton.Visibility = Visibility.Visible;
            this.ToListViewButton.Visibility = Visibility.Collapsed;
        }

        /// <summary>
        /// Handles the click event for the folder navigation mode.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The event arguments.
        /// </param>
        private void FolderListItemClick(object sender, ItemClickEventArgs e)
        {
            var group = e.ClickedItem as PasswordGroup;

            if (group != null)
            {
                this.folderNavigationStack.Push(group);

                this.SetCurrentItems(group);
                this.SetBreadcrumb();
                return;
            }

            var entry = e.ClickedItem as PasswordEntry;

            if (entry != null)
            {
                this.Frame.Navigate(typeof(EntryDetails), entry.Serialize());
            }
        }

        /// <summary>
        /// Sets the current items for the folder navigation mode.
        /// </summary>
        /// <param name="group">
        /// The current group.
        /// </param>
        private void SetCurrentItems(PasswordGroup group)
        {
            this.CurrentItems = group.Groups.Union(group.Entries.Cast<object>()).ToList();
        }

        /// <summary>
        /// Sets the breadcrumb for the folder navigation model.
        /// </summary>
        private void SetBreadcrumb()
        {
            this.Breadcrumb = "/" + string.Join("/", this.folderNavigationStack.Reverse().Skip(1).Select(g => g.GroupName));
        }
    }
}